﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using NeuralNetworkPro.Activation_Functions;

namespace NeuralNetworkPro
{	
    /// <summary>
    /// Mc Culloch and Pitts neuron
    /// </summary>
    /// 
    /// <remarks>Mc Culloch and Pitts neuron computes weighted sum of its inputs
    /// and then applies activation function. The neuron is
    /// usually used in multi-layer neural networks.</remarks>
    [Serializable]
    public class Neuron
    {
        /// <summary>
        /// Neuron's inputs count
        /// </summary>
        protected int inputsCount = 0;

        /// <summary>
        /// Neuron's wieghts
        /// </summary>
        protected double[] weights = null;

        /// <summary>
        /// Neuron's output value
        /// </summary>
        protected double output = 0;

        /// <summary>
        /// Neuron's inputs count
        /// </summary>
        public int InputsCount
        {
            get { return inputsCount; }
        }

        /// <summary>
        /// Neuron's output value
        /// </summary>
        /// 
        /// <remarks>The calculation way of neuron's output value is determined by inherited class.</remarks>
        /// 
        public double Output
        {
            get { return output; }
        }

        protected double inputSum;

        public double InputSum
        {
            get { return inputSum; }
        }
        

        /// <summary>
        /// Neuron's weights accessor
        /// </summary>
        /// 
        /// <param name="index">Weight index</param>
        /// 
        /// <remarks>Allows to access neuron's weights.</remarks>
        /// 
        public double this[int index]
        {
            get { return weights[index]; }
            set { weights[index] = value; }
        }

        /// <summary>
        /// Initialize weight of a neuron 
        /// </summary>
        /// 
        /// <remarks>Initialize neuron's weights 1/sqrt(input_count)
        public virtual void Initialize()
        {
            var rnd = new Random();
            //var val = 1.0 / (double)inputsCount;
            for (int i = 0; i < inputsCount; i++)
            {
                var val = 1.0 / Math.Sqrt((double)inputsCount) + rnd.NextDouble() * 0.1 - 0.05;
                weights[i] = val;
            }
        }

		/// <summary>
		/// Activation function
		/// </summary>
		/// 
		/// <remarks>The function is applied to inputs weighted sum plus
		/// threshold value.</remarks>
		/// 
		protected IActivationFunction function = null;

		/// <summary>
		/// Neuron's activation function
		/// </summary>
		/// 
		public IActivationFunction ActivationFunction
		{
			get { return function; }
            set { function = value; }
		}
		
		/// <summary>
		/// Initializes a new instance of the <see cref="Neuron"/> class
		/// </summary>
		/// 
		/// <param name="inputs">Neuron's inputs count</param>
		/// <param name="function">Neuron's activation function</param>
		/// 
		public Neuron( int inputs, IActivationFunction function )
        {            
            // allocate weights
            inputsCount = Math.Max(1, inputs);
            weights = new double[inputsCount];
            // initialize the neuron
            Initialize();
			this.function = function;
		}

		
		/// <summary>
		/// Computes output value of neuron
		/// </summary>
		/// 
		/// <param name="input">Input vector</param>
		/// 
		/// <returns>Returns neuron's output value</returns>
		/// 
		/// <remarks>The output value of activation neuron is equal to value
		/// of nueron's activation function, which parameter is weighted sum
		/// of its inputs plus threshold value. The output value is also stored
		/// in <see cref="Neuron.Output">Output</see> property.</remarks>
		/// 
        public virtual double Compute(double[] input)
		{
			// check for corrent input vector
			if ( input.Length != inputsCount )
				throw new ArgumentException( );

			// initial sum value
			inputSum = 0.0;

			// compute weighted sum of inputs
			for ( int i = 0; i < inputsCount; i++ )
			{
				inputSum += weights[i] * input[i];
			}

            output = function.Function( inputSum );

			return output;
		}

        internal void RemoveImput(int idx)
        {
            this.inputsCount--;
            this.weights = this.weights.RemoveIdx(idx).ToArray();
        }
    }

    [Serializable]
    public class InputNeuron : Neuron
    {
        int ord = 0;
        public InputNeuron(int ordinal)
            : base(1, new IdentityFunction())
        {
            ord = ordinal;
        }

        public override double Compute(double[] input)
        {
            output = input[ord];
            inputSum = input[ord];
            return input[ord];
        }

        public override void Initialize()
        {
            this.weights[0] = 1;
        }
    }
}
